/**    
 * 文件名：VisualWords.java    
 *    
 * 版本信息：    
 * 日期：2014年3月14日    
 * xyj 足下 xyj 2014     
 * 版权所有    
 *    
 */
package learn.visual.words;

import static com.googlecode.javacv.cpp.opencv_core.CV_32FC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_32S;
import static com.googlecode.javacv.cpp.opencv_core.CV_32SC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_64FC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_TERMCRIT_EPS;
import static com.googlecode.javacv.cpp.opencv_core.CV_TERMCRIT_ITER;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateMat;
import static com.googlecode.javacv.cpp.opencv_core.cvKMeans2;
import static com.googlecode.javacv.cpp.opencv_core.cvReleaseMat;
import static com.googlecode.javacv.cpp.opencv_core.cvTermCriteria;
import static com.googlecode.javacv.cpp.opencv_highgui.cvLoadImage;
import static com.googlecode.javacv.cpp.opencv_legacy.cvCreateKDTree;
import static com.googlecode.javacv.cpp.opencv_legacy.cvFindFeatures;
import static com.googlecode.javacv.cpp.opencv_legacy.cvReleaseFeatureTree;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;

import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvRNG;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_features2d;
import com.googlecode.javacv.cpp.opencv_features2d.DescriptorExtractor;
import com.googlecode.javacv.cpp.opencv_features2d.KeyPoint;
import com.googlecode.javacv.cpp.opencv_imgproc.CvFeatureTree;
import com.googlecode.javacv.cpp.opencv_nonfree;
import com.googlecode.javacv.cpp.opencv_nonfree.SIFT;

/**
 * @项目名称：opencv-test
 * @类名称：VisualWords
 * @类描述：
 * @创建人：zhuyi
 * @创建时间：2014年3月14日 下午5:31:10
 * @修改人：zhuyi
 * @修改时间：2014年3月14日 下午5:31:10
 * @修改备注：
 * @version
 * 
 */
public class VisualWords2 {

    private static final String IMAGE_DIR = "caltech10";

    private static final int DIM = 128;

    private static int SURF_PARAM = 400;

    // cluster数 = visual words的维度数
    private static int MAX_CLUSTER = 500;

    public static void main(String[] args) throws Exception {
        int ret;

        // 从IMAGE_DIR的各个图片特征值抽取
        System.out.println("Load Descriptors ...");
        opencv_features2d.initModule_features2d();
        opencv_nonfree.initModule_nonfree();

        List<Float> data = new ArrayList<Float>();
        CvMat samples = loadDescriptors(data);

        // 局所特徴量をクラスタリングして各クラスタのセントロイドを計算
        System.out.println(samples.rows());
        System.out.println(samples.cols());

        System.out.println("Clustering ...");
        // 各个sample分配聚类标签
        CvMat labels = cvCreateMat(samples.rows(), 1, CV_32S);
        // 各クラスタの中心（セントロイド） DIM次元ベクトル
        CvMat centroids = cvCreateMat(MAX_CLUSTER, DIM, CV_32FC1);
        cvKMeans2(samples, MAX_CLUSTER, labels, cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0), 1,
                new CvRNG(null), 0, centroids, null);

        // 标签不使用，释放之
        cvReleaseMat(labels);

        // 各个图片Visual Words的直方图转换
        // 各个簇的中心向量，用centroids将Visual Words一个个的组成
        System.out.println("Calc Histograms ...");
        calcHistograms(centroids);

    }

    // /**
    // * 从图片文件中抽出SURF特征值
    // *
    // * @param[in] filename 图片文件名
    // * @param[out] imageKeypoints KeyPoints
    // * @param[out] imageDescriptors 各KeyPoint的SURF特征值
    // * @param[out] storage 内存区域
    // * @return int 如果成功，返回0， 失败返回1
    // */
    // public static int extractSURF(String fileName, CvSeq keypoints, CvSeq
    // descriptors, CvMemStorage storage) {
    //
    // // 以灰度方式读取图像
    // IplImage img = cvLoadImage(fileName, CV_LOAD_IMAGE_GRAYSCALE);
    // if (img == null) {
    // throw new UnsupportedOperationException("cannot load image: " +
    // fileName);
    // }
    //
    // CvSURFParams params = cvSURFParams(SURF_PARAM, 1);
    // cvExtractSURF(img, null, keypoints, descriptors, storage, params, 0);
    //
    // return 0;
    // }

    public static CvMat getSiftDesc(String file) {

        IplImage image = cvLoadImage(file);
        KeyPoint keyPoints = new KeyPoint();
        int nFeatures = 400;
        int nOctaveLayers = 3;
        float contrastThreshold = 0.03f;
        int edgeThreshold = 10;
        float sigma = 1.6f;
        SIFT sift = new SIFT(nFeatures, nOctaveLayers, contrastThreshold, edgeThreshold, sigma);
        DescriptorExtractor siftDesc = DescriptorExtractor.create("SIFT");
        sift.detect(image, null, keyPoints);
        CvMat descriptors = new CvMat(null);
        siftDesc.compute(image, keyPoints, descriptors);

        keyPoints = null;

        return descriptors;

    }

    /**
     * 从IMAGE_DIR抽取出来的所有特征值往矩阵(CvMat)中储存
     * 
     * @param[out] samples 特征值的矩阵
     * @param[out] data samples的数据领域
     * @return 成功返回0、失败返回1
     */
    public static CvMat loadDescriptors(List<Float> data) {

        // 打开IMAGE_DIR
        File imageDir = new File(IMAGE_DIR);

        if (!imageDir.exists()) {
            throw new UnsupportedOperationException("cannot open directory: " + IMAGE_DIR);
        }

        // 循环IMAGE_DIR下的图像文件名
        File[] images = imageDir.listFiles();
        CvMat result = null;
        for (File image : images) {
            String fileName = image.getName();
            String filePath = IMAGE_DIR + "/" + fileName;
            // System.out.println(filePath);

            // SIFT抽出
            CvMat desc = getSiftDesc(image.getAbsolutePath());

            // 文件名和特征点数输出
            System.out.println(filePath + "\t" + desc.rows());

            if (result == null) {
                result = desc;
            } else {
                result = MatCombine.combineRows(result, desc);
            }
        }

        return result;
    }

    /**
     * IMAEG_DIR的所有图片的直方图变化并输出 各画像の各局所特徴量を一番近いVisual Wordsに投票してヒストグラムを作成する
     * 
     * @param[in] visualWords Visual Words
     * @return 成功なら0、失敗なら1
     * @throws Exception
     */
    public static int calcHistograms(CvMat visualWords) throws Exception {

        // 一番近いVisual Wordsを高速検索できるようにvisualWordsをkd-treeでインデキシング
        CvFeatureTree ft = cvCreateKDTree(visualWords);

        // 各画像のヒストグラムを出力するファイルを開く
        File hf = new File("histograms.txt");

        // 打开IMAGE_DIR
        File imageDir = new File(IMAGE_DIR);

        if (!imageDir.exists()) {
            throw new UnsupportedOperationException("cannot open directory: " + IMAGE_DIR);
        }

        // 循环IMAGE_DIR下的图像文件名
        File[] images = imageDir.listFiles();
        for (File image : images) {
            String fileName = image.getName();
            String filePath = IMAGE_DIR + "/" + fileName;
            // System.out.println(filePath);

            // 直方图初始化
            int[] histogram = new int[visualWords.rows()];
            for (int i = 0; i < visualWords.rows(); i++) {
                histogram[i] = 0;
            }

            // SURF抽出
            CvMat mat = getSiftDesc(image.getAbsolutePath());

            // 各局所特徴点についてもっとも類似したVisual Wordsを見つけて投票
            int k = 1; // 1-NN
            // もっとも近いVisual Wordsのインデックス
            CvMat indices = cvCreateMat(mat.rows(), k, CV_32SC1);
            // その距離
            CvMat dists = cvCreateMat(mat.rows(), k, CV_64FC1);
            cvFindFeatures(ft, mat, indices, dists, k, 250);
            for (int i = 0; i < indices.rows(); i++) {
                int idx = (int) indices.get(i, 0);
                System.out.println(idx);
                histogram[idx] += 1;
            }

            // ヒストグラムをファイルに出力
            FileUtils.write(hf, filePath + "\t", true);
            for (int i = 0; i < visualWords.rows(); i++) {
                FileUtils.write(hf, (histogram[i] / (float) mat.rows()) + "\t", true);
            }
            FileUtils.write(hf, "\n", true);

            // 後始末
            histogram = null;
            cvReleaseMat(mat);
            cvReleaseMat(indices);
            cvReleaseMat(dists);

        }

        cvReleaseFeatureTree(ft);

        return 0;
    }
}
